/*-------------------------------------------------------------------------
   Copyright (C) 2010 One Laptop per Child

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any
   later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

   In other words, you are welcome to use, share and improve this program.
   You are forbidden to forbid anyone else to use, share and improve
   what you give them.   Help stamp out software-hoarding!

   As a special exception, you may use this file as part of a free software
   library for the XO of the One Laptop per Child project without restriction.
   Specifically, if other files instantiate
   templates or use macros or inline functions from this file, or you compile
   this file and link it with other files to produce an executable, this
   file does not by itself cause the resulting executable to be covered by
   the GNU General Public License.  This exception does not however
   invalidate any other reasons why the executable file might be covered by
   the GNU General Public License.
-------------------------------------------------------------------------*/

#include <cc_compat.h>
#include <stdio.h>
#include "io.h"
#include "timer.h"
#include "sdi.h"
#include "event.h"
#include "ols.h"

#define OLS_ANODE	GPIO_14

#define OLS_REVERSE_BIAS_DELAY		31

#define OLS_STATE_DISABLED			0
#define OLS_STATE_START				1
#define OLS_STATE_STOP				2
#define OLS_STATE_PREWAIT			3
#define OLS_STATE_WAIT				4
#define OLS_STATE_REVERSE_BIAS		5
#define OLS_STATE_REVERSE_BIAS_DELAY 6
#define OLS_STATE_FLOAT_CATHODE		7
#define OLS_STATE_WATCH_FOR_FALL	8
#define OLS_STATE_SMTTEST			9

static xdata int ols_state, ols_state_prev;
static xdata int ols_timer;
static xdata int then, gave, ols_avg, ols_reading;

static xdata unsigned char ols_debug;
static xdata unsigned char ols_assy_test;

xdata unsigned int ols_low_lim, ols_high_lim, ols_ceiling, ols_high_cutoff;

#define MAX(a,b) (((a) > (b)) ? (a) : (b))

int ols_get_reading(void)
{
	return ols_reading;
}

void ols_cmd_read(void)
{
	// printf("rd: %d\n", ols_reading);
	sdi_cmdresp_queue_add(ols_reading & 0xff);
	sdi_cmdresp_queue_add(ols_reading >> 8);
}

void ols_cmd_set_limits(unsigned int low, unsigned int high)
{
	ols_low_lim = low;
	ols_high_lim = high;
	if (ols_high_lim > 10000)
		ols_high_lim = 10000;

	ols_high_cutoff = MAX(ols_high_lim, ols_ceiling);
}

void ols_cmd_get_limits(void)
{
	sdi_cmdresp_queue_add(ols_low_lim & 0xff);
	sdi_cmdresp_queue_add(ols_low_lim >> 8);
	sdi_cmdresp_queue_add(ols_high_lim & 0xff);
	sdi_cmdresp_queue_add(ols_high_lim >> 8);
}

void ols_cmd_set_ceiling(unsigned int ceiling)
{
	ols_ceiling = ceiling;
	if (ols_ceiling > 10000)
		ols_ceiling = 10000;

	ols_high_cutoff = MAX(ols_high_lim, ols_ceiling);
}

void ols_cmd_get_ceiling(void)
{
	sdi_cmdresp_queue_add(ols_ceiling & 0xff);
	sdi_cmdresp_queue_add(ols_ceiling >> 8);
}

void ols_send_report(void)
{
	static unsigned char ols_event;
	if (ols_event != EVENT_OLS_LOW_LIMIT && ols_reading < ols_low_lim)
	{
		ols_event = EVENT_OLS_LOW_LIMIT;
	}
	else if (ols_event != EVENT_OLS_HIGH_LIMIT && ols_reading > ols_high_lim)
	{
		ols_event = EVENT_OLS_HIGH_LIMIT;
	}
	else
	{
		return;
	}

	//if (ols_debug)
	//{
	//	printf("OLS: report %d\n", (uint)ols_event);
	//}

	event_send_event(ols_event);
}

void ols_disable_cathode(void)
{
	if ( board_rev > 0xb2 || board_rev == 0xa1 )
	{
		IO_OUTPUT_LOW(GPIO_30);
	}
	else
	{
		IO_OUTPUT_LOW(GPIO_15);
	}
}

void ols_enable_cathode(void)
{
	if ( board_rev > 0xb2 || board_rev == 0xa1 )
	{
		IO_OUTPUT_HIGH(GPIO_30);
	}
	else
	{
		IO_OUTPUT_HIGH(GPIO_15);
	}
}

void ols_float_cathode(void)
{
	if ( board_rev > 0xb2 || board_rev == 0xa1 )
	{
		IO_PULLUP_DISABLE(GPIO_30);
		IO_INPUT_ONLY(GPIO_30);
	}
	else
	{
		IO_PULLUP_DISABLE(GPIO_15);
		IO_INPUT_ONLY(GPIO_15);
	}

	IO_PULLUP_DISABLE(OLS_ANODE);
	IO_OUTPUT_LOW(OLS_ANODE);
}

unsigned char ols_read_cathode(void)
{
	if (board_rev > 0xb2 || board_rev == 0xa1 )
	{
		return IO_ACTIVE_LOW(GPIO_30);
	}
	else
	{
		return IO_ACTIVE_LOW(GPIO_15);
	}
}

void ols_io_init(void)
{
	if (board_rev > 0xb2 || board_rev == 0xa1 )
	{
		IO_OUTPUT_ENABLE(GPIO_30);
	}
	else
	{
		IO_OUTPUT_ENABLE(GPIO_15);
	}
	IO_OUTPUT_ENABLE(OLS_ANODE);
}

void ols_init_module(void)
{
	// ols_state			= OLS_STATE_DISABLED;
	// ols_state_prev		= OLS_STATE_DISABLED;
	// ols_debug			= 0;
	// ols_avg				= 0;
	// ols_assy_test		= 0;
	// ols_ceiling			= 0;
	// ols_low_lim          = 0;
	// ols_high_lim         = 0;
	ols_io_init();
}

void ols_set_debug(unsigned char value)
{
	ols_debug = value;
}

void ols_start(void)
{
	ols_state = OLS_STATE_START;
}

#ifdef UNUSED
void ols_stop(void)
{
	ols_state = OLS_STATE_STOP;
}
#endif

void ols_cmd_smttest_ledon(void)
{
	ols_forward_bias();
	ols_state = OLS_STATE_SMTTEST;
}

void ols_cmd_smttest_ledoff(void)
{
	ols_reverse_bias();
}

void ols_cmd_smttest_stop(void)
{
	puts("OLS SMT Stop");
	ols_state = OLS_STATE_START;
}

void ols_forward_bias(void) /* (LED forward bias, glows) */
{
	ols_io_init();
	ols_disable_cathode();
	IO_OUTPUT_HIGH(OLS_ANODE);
}

void ols_reverse_bias(void) /* (LED reverse bias, charge, does not glow */
{
	ols_io_init();
	ols_enable_cathode();
	IO_OUTPUT_LOW(OLS_ANODE);
}

void ols_cmd_assytest_start(void)
{
	puts("OLS ASSYTEST Start");
	ols_assy_test = 1;
}

void ols_cmd_assytest_stop(void)
{
	puts("OLS ASSYTEST Stop");
	ols_assy_test = 0;
}

void ols_process(void)
{
	int reading;
	unsigned char cathode;

	switch (ols_state)
	{
		case OLS_STATE_DISABLED:
			break;

		case OLS_STATE_START:
			ols_timer = get_ms_timer();
			ols_state = OLS_STATE_PREWAIT;
			break;

		case OLS_STATE_STOP:
			ols_reverse_bias();
			ols_state = OLS_STATE_DISABLED;
			break;

		case OLS_STATE_PREWAIT:
			// Allow storage led time to work
			disable_LED_INHIBIT();
			ols_timer = get_ms_timer();
			ols_state = OLS_STATE_WAIT;
			break;

		case OLS_STATE_WAIT:
			if (check_timer(ols_timer,500))
			{
				ols_state = OLS_STATE_REVERSE_BIAS;
			}
			break;

		case OLS_STATE_REVERSE_BIAS:
			ols_reverse_bias();
			// Disable the storage LED so it does not interfere
			// with our readings.  But only if we are
			// not in assembly test mode where we want the
			// storage led to affect the OLS sensor.
			if (!ols_assy_test)
			{
				enable_LED_INHIBIT();
			}
			ols_timer = get_ms_timer();
			ols_state = OLS_STATE_REVERSE_BIAS_DELAY;
			break;

		case OLS_STATE_REVERSE_BIAS_DELAY:
			if (check_timer(ols_timer,OLS_REVERSE_BIAS_DELAY))
			{
				ols_state = OLS_STATE_FLOAT_CATHODE;
			}
			break;

		case OLS_STATE_FLOAT_CATHODE:
			/* float the LED cathode */
			ols_float_cathode();
			/* make a note of the time */
			then = get_ms_timer();
			ols_timer = then;
			ols_state = OLS_STATE_WATCH_FOR_FALL;

			/* check if we've not got charge */
			if (ols_read_cathode())
			{
				puts("OLS: no charge");
				ols_state = OLS_STATE_PREWAIT;
			}
			break;

		case OLS_STATE_WATCH_FOR_FALL:

			reading = get_ms_timer() - then;
			cathode = ols_read_cathode();

			if (cathode || reading > ols_high_cutoff)
			{
				static int last_reading;
				/* use simple two-value maximum for a bit of
				 * smoothing.  (use a maximum, because we'd
				 * rather say it's dark out than bright out.)
				 */
				ols_reading = MAX(reading, last_reading);
				last_reading = reading;

				if (ols_debug)
				{
					printf("OLS: cath %d rd %d ols_r %d hc %d\n",
						   (uint)cathode, reading, ols_reading, ols_high_cutoff);
				}

				if (ols_low_lim && ols_high_lim)
					ols_send_report();

				ols_state = OLS_STATE_PREWAIT;
			}
			break;


			// In this state just sit and wait while OFW
			// makes the led blink.
		case OLS_STATE_SMTTEST:
			break;

	}
}
